/*
 * JaamSim Discrete Event Simulation
 * Copyright (C) 2013 Ausenco Engineering Canada Inc.
 * Copyright (C) 2019 JaamSim Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jaamsim.ui;

import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;

import com.jaamsim.basicsim.Entity;
import com.jaamsim.basicsim.Simulation;
import com.jaamsim.input.Input;
import com.jaamsim.input.InputAgent;
import com.jaamsim.input.OutputHandle;
import com.jaamsim.units.DimensionlessUnit;
import com.jaamsim.units.Unit;

public class OutputBox extends FrameBox {
	private static OutputBox myInstance;
	private Entity currentEntity;
	OutputTableModel tableModel;

	private final ArrayList<Object> entries = new ArrayList<>();

	public OutputBox() {
		super( "Output Viewer" );
		setDefaultCloseOperation(FrameBox.DISPOSE_ON_CLOSE);
		addWindowListener(FrameBox.getCloseListener("ShowOutputViewer"));

		tableModel = new OutputTableModel();
		OutputTable table = new OutputTable(tableModel);
		JScrollPane scrollPane = new JScrollPane(table);

		getContentPane().add( scrollPane );

		Simulation simulation = GUIFrame.getJaamSimModel().getSimulation();
		GUIFrame gui = GUIFrame.getInstance();
		Point pt = gui.getGlobalLocation(simulation.getOutputViewerPos().get(0),
				simulation.getOutputViewerPos().get(1));
		setLocation(pt);
		setSize(simulation.getOutputViewerSize().get(0), simulation.getOutputViewerSize().get(1));

		addComponentListener(new ComponentAdapter() {

			@Override
			public void componentMoved(ComponentEvent e) {
				Point pt = gui.getRelativeLocation(getLocation().x, getLocation().y);
				Simulation simulation = GUIFrame.getJaamSimModel().getSimulation();
				simulation.setOutputViewerPos(pt.x, pt.y);
			}

			@Override
			public void componentResized(ComponentEvent e) {
				Simulation simulation = GUIFrame.getJaamSimModel().getSimulation();
				simulation.setOutputViewerSize(getSize().width, getSize().height);
			}
		});
	}

	/**
	 * Returns the only instance of the property box
	 */
	public synchronized static OutputBox getInstance() {
		if (myInstance == null)
			myInstance = new OutputBox();

		return myInstance;
	}

	@Override
	public void setEntity( Entity entity ) {
		currentEntity = entity;
		if (currentEntity == null) {
			setTitle("Output Viewer");
			entries.clear();
			return;
		}
		setTitle("Output Viewer - " + currentEntity.getName());

		// Build up the row list, leaving extra rows for entity names
		Class<?> currClass = null;
		entries.clear();

		ArrayList<OutputHandle> handles = OutputHandle.getOutputHandleList(currentEntity);
		for (OutputHandle h : handles) {
			Class<?> klass = h.getDeclaringClass();
			if (currClass != klass) {
				// This is the first time we've seen this class, add a place holder row
				currClass = klass;
				entries.add(klass);
			}
			entries.add(h);
		}
	}

	@Override
	public void updateValues(double simTime) {
		if (tableModel == null) return;
		tableModel.simTime = simTime;
		tableModel.fireTableDataChanged();
	}

	private synchronized static void killInstance() {
		myInstance = null;
	}

	@Override
	public void dispose() {
		killInstance();
		super.dispose();
	}

	private class OutputTable extends JTable {
		public OutputTable(TableModel model) {
			super(model);

			setDefaultRenderer(Object.class, colRenderer);

			getColumnModel().getColumn(0).setWidth(150);
			getColumnModel().getColumn(1).setWidth(100);

			this.getTableHeader().setFont(FrameBox.boldFont);
			this.getTableHeader().setReorderingAllowed(false);
		}

		@Override
		public String getToolTipText(MouseEvent event) {
			Point p = event.getPoint();
			int row = rowAtPoint(p);
			if (currentEntity == null ||
			    row >= entries.size() ||
			    entries.get(row) instanceof Class) {
				return null;
			}

			OutputHandle output = (OutputHandle)entries.get(row);
			return GUIFrame.formatOutputToolTip(output.getName(), output.getDescription());
		}

		@Override
		public void doLayout() {
			FrameBox.fitTableToLastColumn(this);
		}
	}

	private class OutputTableModel extends AbstractTableModel {
		double simTime = 0.0d;
		@Override
		public int getColumnCount() {
			return 2;
		}

		@Override
		public String getColumnName(int column) {
			switch (column) {
			case 0: return "Output";
			case 1: return "Value";
			}

			return "Unknown";
		}

		@Override
		public int getRowCount() {
			return entries.size();
		}

		@Override
		public Object getValueAt(int row, int col) {
			Object entry = entries.get(row);
			switch (col) {
			case 0:
				if (entry instanceof Class)
					return String.format("<HTML><B>%s</B></HTML>", ((Class<?>)entry).getSimpleName());
				return String.format("    %s", ((OutputHandle)entry).getName());
			case 1:
				if (entry instanceof Class)
					return "";
				try {
					// Determine the preferred unit
					OutputHandle out = (OutputHandle)entry;
					Class<? extends Unit> ut = out.getUnitType();
					double factor = Unit.getDisplayedUnitFactor(ut);

					// Select the appropriate format
					String fmt = "%s";
					if (out.isNumericValue()) {
						if (out.isIntegerValue() && out.getUnitType() == DimensionlessUnit.class) {
							fmt = "%.0f";
						}
						else {
							fmt = "%g";
						}
					}

					// Evaluate the output
					StringBuilder sb = new StringBuilder();
					sb.append(InputAgent.getValueAsString(out, simTime, fmt, factor));

					// Append the appropriate unit
					if (ut != Unit.class && ut != DimensionlessUnit.class) {
						String unitString = Unit.getDisplayedUnit(ut);
						sb.append(Input.SEPARATOR).append(unitString);
					}

					return sb.toString();
				}
				catch (Throwable e) {
					return "Cannot evaluate";
				}
			default:
				assert false;
				return null;
			}
		}

		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return false;
		}
	}

}
